﻿#region Namespace Imports


// Standard class imports
using System;
using System.Collections.Generic;
using System.Text;

// Eases access to localization and globalization types
using System.Globalization;

// For access to some declarative security types
using System.Security.Permissions;

// Simplifies access to SharePoint types
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Security;

// Alias the assembly resources to shorten references.
using res = SPMcDonough.Moss.BlobCacheFarmFlush.Properties.Resources;


#endregion Namespace Imports


namespace SPMcDonough.Moss.BlobCacheFarmFlush
{


    /// <summary>
    /// This SPJobDefinition subclass is used to carry out the actual process of modifying
    /// properties on the SPWebApplication of a site collection upon which the Feature was
    /// activated.  This property modification is handled in a timer job in order to provide
    /// the operation with a security context (farm service account running within the 
    /// OWSTIMER process) that will be free of any security-related issues.
    /// <para>In terms of operations, this timer job examines each of the site collections
    /// within the target web application to see if it possesses a flag indicating that a
    /// BLOB cache flush is requested.  If any of the site collections have set the flag,
    /// the flag is cleared and the timer job takes care of incrementing the appropriate
    /// web application property.</para>
    /// </summary>
    /// <remarks>The basics of this operation were constructed from information that
    /// was presented by Maxime Bombardier in her blog post titled "How to really
    /// clear the BlobCache on all servers in the farm."  The post can be found at
    /// http://blogs.msdn.com/maximeb/archive/2008/08/13/how-to-really-clear-the-blobcache-on-all-servers-in-the-farm.aspx
    /// <para>The mechanics of how to implement a custom timer job were also picked
    /// up from a combination of sources.  Two in particular are worth mentioning.  First,
    /// Andrew Connell presented the material nicely at 
    /// http://www.andrewconnell.com/blog/articles/CreatingCustomSharePointTimerJobs.aspx.
    /// Second, I picked-up a few tips and words of wisdom from one of my team members,
    /// Steve Pietrek, in a presentation he assembled for the local Cincinnati SharePoint
    /// User Group.  He also took this on the road to a couple/few SharePoint Saturday presentations:
    /// http://cid-cb8214667285e9de.skydrive.live.com/self.aspx/Presentations/timerjobs.pdf</para>
    /// </remarks>
    [SharePointPermission(SecurityAction.LinkDemand, ObjectModel=true),
     SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel=true)]
    public class BlobCacheFarmFlushTimerJob : SPJobDefinition
    {


        #region Constructors


        /// <summary>
        /// Default constructor implementation; simply a pass-through.  In the event that
        /// we were to add persisted properties, this constructor would be needed for
        /// serialization support.
        /// </summary>
        public BlobCacheFarmFlushTimerJob()
            : base()
        { }


        /// <summary>
        /// This is the constructor that we'll be using to actually create instances of our
        /// timer job.  We'll be tying instances of the timer job to specific web applications,
        /// so we'll use the overload that supports this mode of operation.
        /// </summary>
        public BlobCacheFarmFlushTimerJob(String name, SPWebApplication webApplication, SPServer server, SPJobLockType lockType)
            : base(name, webApplication, server, lockType)
        { }


        #endregion Constructors


        #region Overrides (SPJobDefinition)


        /// <summary>
        /// This is the method that is invoked when the timer job instances is directed to
        /// run.  It is here that we'll be performing the sweep of the site collections to
        /// determine if a flush is called for; if it is, we'll update all properties
        /// accordingly.
        /// </summary>
        /// <param name="targetInstanceId">A GUID that identifies the content database upon
        /// which the timer job is being run.  Since the timer job will be run at the web
        /// app level, we won't really need this parameter for anything.</param>
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel=true)]
        public override void Execute(Guid targetInstanceId)
        {
            // Begin by grabbing a reference to the housing SPWebApplication.  Assuming
            // we have a valid web app reference, we'll continue.
            SPWebApplication housingWebApp = this.Parent as SPWebApplication;
            if (housingWebApp != null)
            {
                // Begin by determining if we need to perform a flush.
                if (IsFlushRequested(housingWebApp))
                {
                    // It looks like at least one of the site collections within the
                    // web application is requesting a flush.  Increment the flush
                    // counter.
                    IncrementFarmFlushCounter(housingWebApp);

                    // The final step is to assign the current date and time to the
                    // "last flush" web app property.
                    housingWebApp.Properties[res.WEBAPP_PROPERTY_LASTFLUSH] = DateTime.Now.ToString(CultureInfo.InvariantCulture);
                    housingWebApp.Update();
                }
            }
        }


        #endregion Overrides (SPJobDefinition)


        #region Methods (Private, Static)


        /// <summary>
        /// This method is used to increment the farm blob flush counter by one.
        /// Doing so tells the BLOB cache flush infrastructure that a file system
        /// purge and bin file resets need to take place.
        /// </summary>
        /// <param name="targetWebApp">The SPWebApplication object for which a
        /// property change will be applied to indicate that a flush is needed.</param>
        private static void IncrementFarmFlushCounter(SPWebApplication targetWebApp)
        {
            // Begin by setting up a variable with a default value for the number
            // of flushes.
            Int32 flushCount = 0;

            // Does the flush count key exist for the current web application?
            if (targetWebApp.Properties.ContainsKey(res.WEBAPP_PROPERTY_FLUSHCOUNT))
            {
                // The current web application contains the key of interest.  Attempt
                // to pull a usable value out; if the attempt fails, the flushCount will
                // still have a value of zero assigned.
                Int32.TryParse(targetWebApp.Properties[res.WEBAPP_PROPERTY_FLUSHCOUNT].ToString(),
                               NumberStyles.Integer,
                               CultureInfo.InvariantCulture,
                               out flushCount);
            }
            
            // At this point, the flushCount variable contains either a zero (to indicate
            // that a flush hasn't ever been performed or the value specified isn't a
            // recognizable Int32), or the recognized last flush count.  Either way, we
            // need to increment it by one and store it back.  Incrementing the value (or
            // assigning it for the first time) is what signals the flush.
            flushCount++;
            targetWebApp.Properties[res.WEBAPP_PROPERTY_FLUSHCOUNT] = flushCount.ToString(CultureInfo.InvariantCulture);
            targetWebApp.Update();
        }


        /// <summary>
        /// This method is used to examine each of the site collections within the
        /// specified web application to determine if any of them are requesting a
        /// BLOB cache flush.
        /// </summary>
        /// <param name="targetWebApp">An SPWebApplication object that will have
        /// its site collections examined determine if any of them are requesting
        /// a flush.</param>
        /// <returns>TRUE if one or more of the site collections within
        /// <paramref name="targetWebApp"/> is requesting a farm-level BLOB cache
        /// flush, FALSE if not.</returns>
        /// <remarks>A side-effect of executing this method is that all web
        /// applications that have their flush flag set to TRUE will have it
        /// toggled back to FALSE.</remarks>
        private static Boolean IsFlushRequested(SPWebApplication targetWebApp)
        {
            // Begin by setting a flag that will be used to indicate whether or not
            // a flush is being requested by any of the site collections within the
            // the web app; once that's done, begin the process of examining the
            // site collections.
            Boolean isFlushRequested = false;
            for (Int32 siteIndex = 0; siteIndex < targetWebApp.Sites.Count; siteIndex++)
            {
                // Since we're explicitly opening sites and webs, we'll need to carry
                // out the required disposals.
                using (SPSite currentSite = targetWebApp.Sites[siteIndex])
                {
                    using (SPWeb rootWeb = currentSite.RootWeb)
                    {
                        // See if the flush flag exists on the RootWeb.
                        if (rootWeb.AllProperties.ContainsKey(res.ROOTWEB_PROPERTY_FLUSHFLAG))
                        {
                            // The property is present.  We'll parse it out and attempt to
                            // determine if a flush is required.
                            Boolean isCurrentWebRequestingFlush = false;
                            if (Boolean.TryParse(rootWeb.Properties[res.ROOTWEB_PROPERTY_FLUSHFLAG], out isCurrentWebRequestingFlush))
                            {
                                // Parsing was successful, but we don't know if the String that was parsed
                                // contains TRUE or FALSE.
                                if (isCurrentWebRequestingFlush)
                                {
                                    // The current site collection is requesting a flush, so we'll 
                                    // make sure that the method-level flag is set.  We'll also reset
                                    // the flag on the current RootWeb to FALSE to indicate that the
                                    // request has been processed.
                                    isFlushRequested = true;
                                    rootWeb.Properties[res.ROOTWEB_PROPERTY_FLUSHFLAG] = Boolean.FalseString;
                                    rootWeb.Properties.Update();
                                }
                            }

                            else
                            {
                                // Though the RootWeb has the appropriate key on it, the value for
                                // that key makes no sense (i.e., can't be parsed as TRUE or FALSE).
                                // We need to flip the value back to FALSE to remove processing problems
                                // on the next pass.
                                rootWeb.Properties[res.ROOTWEB_PROPERTY_FLUSHFLAG] = Boolean.FalseString;
                                rootWeb.Properties.Update();
                            }
                        }
                    }
                }
            }

            // Return the result of the operation (TRUE if a flush should be performed,
            // FALSE if not).
            return isFlushRequested;
        }



        #endregion Methods (Private, Static)


    }
}
